package ru.papergames.battleserver.model.battle.npc;

import org.apache.log4j.Logger;
import ru.papergames.Config;
import ru.papergames.battleserver.model.battle.AbstractBattle;
import ru.papergames.battleserver.model.battle.BattlePlayer;
import ru.papergames.battleserver.model.battle.npc.processors.NpcActionProcessor;

/**
 * TODO: add description
 * Date: 11.05.12 21:09
 *
 * @author Alexander Troshanin
 */
public class NpcsControllerThread extends Thread {

    private static Logger log = Logger.getLogger(NpcsControllerThread.class.getName());

    public static final int SLEEP_TIME = Config.MILLIS_IN_TICK * 5;

    /**
     * Once initialized, this array will never be changed.
     * We will only change fields of its elements - BattleHolder(s)
     */
    private final NpcHolder [] npcHolders;

    private volatile boolean shuttingDown;
    private volatile boolean active;
    private int activeNpcCnt;
    private final Object lock = new Object();

    /**
     * Statistic varaibles
     */
    private long minDelta;
    private long maxDelta;
    private long avgDelta;
    private int counter;

    public NpcsControllerThread() {
        npcHolders = new NpcHolder[Config.NPC_COUNT_IN_ONE_NPC_CONTROLLER];
        for (int i = 0; i< npcHolders.length; i++) {
            npcHolders[i] = new NpcHolder();
        }
    }

    public boolean addNpcToProcess(BattlePlayer npcPlayer) {
        if (npcPlayer.isPcPlayer()) {
            throw new IllegalArgumentException("BattlePlayer is PC");
        }
        if (npcPlayer.getBattle().isBattleFinished() ||
                npcPlayer.getActionProcessor() == null) {
            // if battle is finished. OR THIS IS NOT NPC
            return false;
        }
        for (NpcHolder npcHolder : npcHolders) {
            if (!npcHolder.isHolderBussy()) {
                synchronized (npcHolder) {
                    if (!npcHolder.isHolderBussy()) {
                        npcHolder.setNpcPlayer(npcPlayer);
                        npcHolder.setBattle(npcPlayer.getBattle());
                        npcHolder.setHolderBussy(true);
                        activeNpcCnt++;
                        activateIfNotActive();
                        return true;
                    }
                }
            }
        }

        return false;
    }

    public void run() {
        log.info("NpcControllerThread.run. Start turn controller thread");
        initStatisticVars();
        while(!shuttingDown) {
            waitIfNotActive();
            if (!shuttingDown) {
                long start = System.currentTimeMillis();
                try {
                    processNpcs();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                long delta = System.currentTimeMillis() - start;
                processStatistic(delta);
                //log.info("delta:" + delta);
                if (delta > SLEEP_TIME - 1) {
                    log.warn("ALARM!!!! - NPCs were processed in NpcControllerThread more than [" +
                            (SLEEP_TIME - 1) + "ms]. process time: " + delta);
                    delta = SLEEP_TIME - 1;
                }

                sleepAfterProcess(SLEEP_TIME - delta);
            }
        }
        log.info("NpcControllerThread.run. FINISH turn controller thread");
    }

    private void processNpcs() {
        for (int i=0; i<npcHolders.length; i++) {
            NpcHolder npcHolder = npcHolders[i];
            if (npcHolder.isHolderBussy()) {
                BattlePlayer npcPlayer = npcHolder.getNpcPlayer();
                AbstractBattle battle = npcHolder.getBattle();
                if (battle.isBattleFinished()) {
                    npcHolder.clearHolder();
                    npcHolder.setHolderBussy(false);
                    activeNpcCnt-- ;
                    activeNpcCnt = activeNpcCnt <= 0 ? 0 : activeNpcCnt;
                    active = activeNpcCnt != 0;
                } else if (battle.isPlayerMovingNow(npcPlayer)) {
                    // Ok here we know that One or more NPC soldiers alive and its theirs turn
                    // So now they can do something bad for others;-)
                    NpcActionProcessor actionProcessor = npcPlayer.getActionProcessor();
                    actionProcessor.processNpcAction(battle, npcPlayer);
                }

            } // end if holder is bussy
        } // end for

    }

    private void waitIfNotActive() {
        if (!active) {
            synchronized (lock) {
                if (!active && activeNpcCnt == 0) {
                    try {
                        log.info("NpcControllerThread.waitIfNotActive. Start waiting");
                        lock.wait();
                        log.info("NpcControllerThread.waitIfNotActive. Got battles. Starting to work again.");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    private void activateIfNotActive() {
        if (!active) {
            synchronized (lock) {
                active = true;
                lock.notifyAll();
            }
        }
    }

    public void sleepAfterProcess(long millis) {
        try {
            sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void initStatisticVars() {
        minDelta = 100000;
        maxDelta = -1;
        avgDelta = 0;
        counter = 0;
    }

    private void processStatistic(long delta) {
        minDelta = (delta < minDelta) ? delta : minDelta;
        maxDelta = (delta > maxDelta) ? delta : maxDelta;
        avgDelta += delta;
        counter++;
        if (counter >= 50) {
            avgDelta = (avgDelta - minDelta - maxDelta) / counter;
            log.info("NpcsControllerThread: DELTA INFO(activeNpcCnt[" + activeNpcCnt + "]): min[" + minDelta +"], max[" + maxDelta + "], avg[" + avgDelta + "]");
            initStatisticVars();
        }
        //log.info("delta:" + delta);
    }

    public void shutDown() {
        this.shuttingDown = true;
        synchronized (lock) {
            lock.notify();
        }
    }

}
